package utils

import (
	"bufio"
	"errors"
	"io"
	"unicode"
)

const (
	TYPE_UNKNOW    = 0
	TYPE_STATEMENT = 1
	TYPE_KEYWORD   = 2
	TYPE_FUNCTION  = 3
)

var SqlKeywords = map[string]int{
	"SELECT":   TYPE_STATEMENT,
	"FROM":     TYPE_STATEMENT,
	"WHERE":    TYPE_STATEMENT,
	"ORDER BY": TYPE_STATEMENT,
	"LIMIT":    TYPE_STATEMENT,
	"OFFSET":   TYPE_STATEMENT,
	"ALL":      TYPE_STATEMENT,
	"AS":       TYPE_STATEMENT,
	"DISTINCT": TYPE_STATEMENT,

	"AVG":   TYPE_FUNCTION,
	"COUNT": TYPE_FUNCTION,
}

type TokenReader struct {
	reader  *bufio.Reader
	inQuote rune
}

func NewTokenReader(r io.Reader) *TokenReader {
	return &TokenReader{reader: bufio.NewReader(r), inQuote: 0}
}

func (tr *TokenReader) Read() (rune, error) {
	ch, _, err := tr.reader.ReadRune()
	if err != nil {
		return 0, err
	}
	return ch, nil
}

func (tr *TokenReader) UnreadRune() error {
	return tr.reader.UnreadRune()
}

func (tr *TokenReader) SkipSpace() error {
	for {
		ch, _, err := tr.reader.ReadRune()
		if err != nil {
			return err
		}
		if !unicode.IsSpace(ch) {
			tr.UnreadRune()
			return nil
		}
	}
}

func (tr *TokenReader) ReadString(delim rune) (string, error) {
	token := ""
	for {
		ch, _, err := tr.reader.ReadRune()
		if err != nil {
			return "", err
		}
		token += string(ch)
		if ch == delim {
			return token, nil
		}
	}
}

func (tr *TokenReader) ReadParm() (string, error) {
	token := ""
	ch, _, err := tr.reader.ReadRune()
	if err != nil {
		return "", err
	}
	if ch != '{' {
		tr.UnreadRune()
		return "", errors.New("invalid character '{' in parm")
	} else {
		token += string(ch)
	}

	for {
		ch, _, err := tr.reader.ReadRune()
		if err != nil {
			return "", err
		}
		if unicode.IsSpace(ch) {
			continue
		}
		token += string(ch)
		if ch == '}' {
			return token, nil
		}
	}
}

func (tr *TokenReader) ReadToken() (string, error) {
	token := ""
	for {
		ch, _, err := tr.reader.ReadRune()
		if err != nil {
			return "", err
		}

		switch {
		case unicode.IsSpace(ch):
			if len(token) > 0 {
				return token, nil
			} else {
				continue
			}
		case ch == '\'' || ch == '`':
			token += string(ch)
			str, err := tr.ReadString(ch)
			if err != nil {
				return "", err
			}
			token += str
			return token, nil
		case ch == '$':
			token += string(ch)
			str, err := tr.ReadParm()
			if err != nil {
				return "", err
			}
			token += str
			return token, nil
		case unicode.IsPunct(ch):
			if len(token) > 0 {
				tr.UnreadRune()
				return token, nil
			}
			return string(ch), nil
		default:
			token += string(ch)
		}
	}
}
